package com.bestcem.xm.txtanls.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.math.MathUtil;
import com.bestcem.xm.common.core.domain.web.ServiceResult;
import com.bestcem.xm.txtanls.dao.*;
import com.bestcem.xm.txtanls.dao.dto.RecordSentiDTO;
import com.bestcem.xm.txtanls.entity.pojo.JoinInfoViewpointDO;
import com.bestcem.xm.txtanls.entity.pojo.ProjectDO;
import com.bestcem.xm.txtanls.entity.pojo.TopicDO;
import com.bestcem.xm.txtanls.entity.pojo.ViewpointDO;
import com.bestcem.xm.txtanls.enums.PeriodTypeEnum;
import com.bestcem.xm.txtanls.grpc.service.param.chart.*;
import com.bestcem.xm.txtanls.grpc.v1.enums.*;
import com.bestcem.xm.txtanls.service.ChartService;
import com.bestcem.xm.txtanls.service.KeywordService;
import com.bestcem.xm.txtanls.service.dto.RecordSearchDTO;
import com.bestcem.xm.txtanls.service.dto.chart.*;
import com.bestcem.xm.txtanls.util.ChartUtil;
import com.bestcem.xm.txtanls.util.ViewpointUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static com.bestcem.xm.common.core.constant.Constants.DEFAULT_MAP_SIZE;

/**
 * 文本分析接口实现类
 *
 * @author jy.zhao
 * @version 1.0
 * @date 2021/5/13 13:49
 **/
@Service
@Slf4j
public class ChartServiceImpl implements ChartService {

    /**
     * 此处的key为map中对应的key。只有此类使用。
     * RECORD_SENTI_MIX_KEY 混合情感，由逻辑计算得出
     * NEGATIVE、NEUTRAL、POSITIVE 数据库中存在
     */
    private static final String RECORD_SENTI_MIX_KEY = "2";
    private static final String RECORD_SENTI_POSITIVE_KEY = "1";
    private static final String RECORD_SENTI_NEGATIVE_KEY = "-1";
    private static final String RECORD_SENTI_NEUTRAL_KEY = "0";
    private static final String RECORD_SENTI_TOTAL_NUM_KEY = "total_num";
    /**
     * 获取情感趋势时根据不同的时间间隔查询的周期数量
     */
    private static final Integer INTERVAL_DAY_PERIOD_COUNT = 31;
    private static final Integer INTERVAL_MONTH_PERIOD_COUNT = 12;
    private static final Integer INTERVAL_YEAR_PERIOD_COUNT = 10;
    private static final Integer VIEW_EVALUATE_PERIOD_COUNT = 7;
    /**
     * 话题关联时两个话题之间的分割符号
     */
    private static final String TOPIC_SEPARATOR = "-";

    /**
     * 词云图返回结果的数量
     */
    private static final Integer WORD_CLOUD_RESULT_LIMIT = 50;
    /**
     * 话题关联返回结果的数量
     */
    private static final Integer TOPIC_RELATION_RESULT_COUNT = 15;

    /**
     * 话题分析Top n=5
     */
    private static final Integer TOPIC_ANALYSIS_RESULT_COUNT = 5;
    /**
     * 默认map初始化大小
     */
    private static final int DEFAULT_MAP_INITIAL_CAPACITY = 1 << 4;

    @Resource
    private RecordDao recordDao;
    @Resource
    private ViewpointDao viewPointDao;
    @Resource
    private JoinInfoDao joinInfoDao;
    @Resource
    private HiddenKeywordDao hiddenKeywordDao;
    @Resource
    private TopicDao topicDao;
    @Resource
    private IndustryToKeywordDao industryToKeywordDao;
    @Resource
    private AnalysisProjectDao analysisProjectDao;


    @Resource
    private KeywordService keywordService;

    @Override
    public ServiceResult<ViewpointStatResultDTO> statViewpoint(BaseChartParam baseChartParam) {
        String projectId = baseChartParam.getProjectId();
        List<String> fieldIds = analysisProjectDao.listTextFiledId(projectId, baseChartParam.getFieldId());
        // 查询观点列表，把结果组织起来
        List<ViewpointDO> viewPoints = viewPointDao.listViewpoints(projectId);
        // 根据观点组和观点。把结果id组织起来。String为观点组id，Set中为观点id
        Map<String, Set<String>> viewPointRelation = viewPoints.stream().filter(item -> Validator.isNotEmpty(item.getGroupId())).collect(Collectors.groupingBy(ViewpointDO::getGroupId, Collectors.mapping(ViewpointDO::getId, Collectors.toSet())));
        // map中存放观点id和对应的名字，避免在for循环里通过list去找
        Map<String, String> viewPointIdNameMap = viewPoints.stream().filter(item -> Validator.isNotEmpty(item.getViewContent())).collect(Collectors.toMap(ViewpointDO::getId, ViewpointDO::getViewContent, (v1, v2) -> v2));
        // 查询原话记录
        RecordSearchDTO recordSearchDTO = getRecordSearchDTO(projectId, fieldIds, baseChartParam.getTimeRangeDTO().getPeriodTime());
        List<String> recordIds = recordDao.listRecordIds(recordSearchDTO);
        int recordCount = recordIds.size();
        // 统计原话对应的观点，统计结果String为观点id，Map为统计结果
        // 不能直接判断原话数量就返回空数组，最终要返回观点的名称等内容
        Map<String, Map<String, Integer>> viewPointStatMap = new HashMap<>(DEFAULT_MAP_SIZE);
        if (recordCount > 0) {
            viewPointStatMap = statViewpoint(projectId, viewPointRelation, recordIds);
        }

        // 格式化统计结果，转换为观点组+观点的层级结构
        List<ViewpointGroupStatDTO> viewpointGroupStatDTOS = formatViewpointStatResult(recordCount, viewPointStatMap, viewPointRelation, viewPointIdNameMap);

        return ServiceResult.success(new ViewpointStatResultDTO(viewpointGroupStatDTOS, recordCount));
    }

    @Override
    public ServiceResult<ViewpointTopDTO> viewpointTop(PeriodChartParam periodChartParam) {
        long start = System.currentTimeMillis();
        String projectId = periodChartParam.getProjectId();
        // 查询观点列表，把结果组织起来
        List<ViewpointDO> viewPoints = viewPointDao.listViewpoints(projectId);
        Map<String, String> viewPointIdNameMap = viewPoints.stream().filter(item -> Validator.isNotEmpty(item.getViewContent())).collect(Collectors.toMap(ViewpointDO::getId, ViewpointDO::getViewContent, (v1, v2) -> v2));
        // 周期时间
        PeriodTimeDTO periodTime;
        // 如果计算的是上期。计算上期开始结束时间
        if (periodChartParam.getPeriod() == PeriodTypeEnum.CURRENT.getIndex()) {
            periodTime = periodChartParam.getTimeRangeDTO().getPeriodTime();
        } else {
            periodTime = ChartUtil.getLastPeriodTime(periodChartParam.getTimeRangeDTO());
        }
        List<String> fieldIds = analysisProjectDao.listTextFiledId(projectId, periodChartParam.getFieldId());
        // 查询原话记录
        RecordSearchDTO recordSearchDTO = getRecordSearchDTO(projectId, fieldIds, periodTime);
        List<String> recordIds = recordDao.listRecordIds(recordSearchDTO);
        int recordCount = recordIds.size();
        // 统计原话对应的观点，统计结果String为观点id，Map为统计结果
        // 不能直接判断原话数量就返回空数组，最终要返回观点的名称等内容
        Map<String, Map<String, Integer>> viewPointStatMap = new HashMap<>(DEFAULT_MAP_SIZE);
        if (recordCount > 0) {
            viewPointStatMap = statChildViewpoint(projectId, recordIds);
        }
        List<ViewpointEvaluationTopDTO> goodEvaluationList = new ArrayList<>();
        List<ViewpointEvaluationTopDTO> badEvaluationList = new ArrayList<>();
        Map<String, String> viewShowAliasMap = ViewpointUtil.VIEW_SHOW_ALIAS_MAP;
        for (Map.Entry<String, Map<String, Integer>> viewPointStat : viewPointStatMap.entrySet()) {
            Map<String, Integer> viewpointStatInfo = viewPointStat.getValue();
            String viewpointId = viewPointStat.getKey();
            String viewpointName = viewPointIdNameMap.get(viewpointId);
            Integer negNum = viewpointStatInfo.get(RECORD_SENTI_NEGATIVE_KEY);
            Integer posNum = viewpointStatInfo.get(RECORD_SENTI_POSITIVE_KEY);
            Integer totalNum = viewpointStatInfo.get(RECORD_SENTI_TOTAL_NUM_KEY);
            ViewpointEvaluationTopDTO goodEvaluation = new ViewpointEvaluationTopDTO(viewpointId, viewShowAliasMap.getOrDefault(viewpointName, viewpointName), ChartUtil.calPercentage2(posNum, totalNum));
            ViewpointEvaluationTopDTO badEvaluation = new ViewpointEvaluationTopDTO(viewpointId, viewShowAliasMap.getOrDefault(viewpointName, viewpointName), ChartUtil.calPercentage2(negNum, totalNum));
            goodEvaluationList.add(goodEvaluation);
            badEvaluationList.add(badEvaluation);
        }
        List<ViewpointEvaluationTopDTO> goodEvaluations = goodEvaluationList.stream().sorted((ratio1, ratio2) -> ratio2.getRatio().compareTo(ratio1.getRatio())).limit(5).collect(Collectors.toList());
        List<ViewpointEvaluationTopDTO> badEvaluations = badEvaluationList.stream().sorted((ratio1, ratio2) -> ratio2.getRatio().compareTo(ratio1.getRatio())).limit(5).collect(Collectors.toList());

        log.error("文本分析top5调用耗时：{}", System.currentTimeMillis() - start);
        return ServiceResult.success(new ViewpointTopDTO(goodEvaluations, badEvaluations));
    }

    @Override
    public ServiceResult<ViewPointEvaluateDTO> statViewpointEvaluate(ViewpointEvaluateParam viewpointEvaluateParam) {
        ViewPointEvaluateDTO viewPointEvaluateDTO = new ViewPointEvaluateDTO();
        List<String> fieldIds = analysisProjectDao.listTextFiledId(viewpointEvaluateParam.getProjectId(), viewpointEvaluateParam.getFieldId());
        // 获取本期开始结束时间
        PeriodTimeDTO curPeriodTime = viewpointEvaluateParam.getTimeRangeDTO().getPeriodTime();
        // 获取上期开始结束时间
        PeriodTimeDTO lastPeriodTime = ChartUtil.getLastPeriodTime(viewpointEvaluateParam.getTimeRangeDTO());

        // 查询观点列表，把结果组织起来
        List<ViewpointDO> viewPointEntities = viewPointDao.listViewpoints(viewpointEvaluateParam.getProjectId());
        // 根据观点组和观点。把结果id组织起来。String为观点组id，List中为观点id
        Map<String, Set<String>> viewPointRelation = viewPointEntities.stream().filter(item -> Validator.isNotEmpty(item.getGroupId())).collect(Collectors.groupingBy(ViewpointDO::getGroupId, Collectors.mapping(ViewpointDO::getId, Collectors.toSet())));

        // 前端需要展示的观点列表Cha
        List<ViewpointDO> viewPointToShow;
        // 过滤。viewType=GROUP_VALUE 查询观点组
        Integer viewType = viewpointEvaluateParam.getViewpointType();
        if (Validator.isNotEmpty(viewpointEvaluateParam.getViewpointGid())) {
            viewPointToShow = viewPointEntities.stream().filter(item -> viewpointEvaluateParam.getViewpointGid().equals(item.getGroupId())).collect(Collectors.toList());
        } else if (ViewpointTypeEnum.ViewpointType.VIEW_VALUE == viewType) {
            viewPointToShow = viewPointEntities.stream().filter(item -> item.getType() == ViewpointTypeEnum.ViewpointType.VIEW_VALUE).collect(Collectors.toList());
        } else {
            viewPointToShow = viewPointEntities.stream().filter(item -> item.getType() == ViewpointTypeEnum.ViewpointType.GROUP_VALUE).collect(Collectors.toList());
        }

        // 获取本期观点变化数据
        List<StatDTO> curPeriodData = statViewEvaluatePeriodData(viewpointEvaluateParam, fieldIds, viewPointToShow, viewPointRelation, curPeriodTime);
        // 获取本期观点变化数据
        List<StatDTO> lastPeriodData = statViewEvaluatePeriodData(viewpointEvaluateParam, fieldIds, viewPointToShow, viewPointRelation, lastPeriodTime);

        viewPointEvaluateDTO.setCurPeriod(curPeriodTime);
        viewPointEvaluateDTO.setLastPeriod(lastPeriodTime);
        // merge本期、上期观点评价变化，
        List<ViewPointEvaluateRatioDTO> evaluateRatioDTOS = mergeViewpointEvaluateStatDTO(curPeriodData, lastPeriodData);
        // 对结果进行排序
        if (!evaluateRatioDTOS.isEmpty()) {
            if (Validator.isNotEmpty(viewpointEvaluateParam.getSortKey())) {
                if (SortKeyEnum.SortKey.PREVIOUS.name().equals(viewpointEvaluateParam.getSortKey())) {
                    evaluateRatioDTOS = evaluateRatioDTOS.stream().sorted((x, y) -> y.getLastRatio() - x.getLastRatio()).collect(Collectors.toList());
                } else if (SortKeyEnum.SortKey.CURRENT.name().equals(viewpointEvaluateParam.getSortKey())) {
                    evaluateRatioDTOS = evaluateRatioDTOS.stream().sorted((x, y) -> y.getCurRatio() - x.getCurRatio()).collect(Collectors.toList());
                } else if (SortKeyEnum.SortKey.COMPARE_PREVIOUS.name().equals(viewpointEvaluateParam.getSortKey())) {
                    evaluateRatioDTOS = evaluateRatioDTOS.stream().sorted((x, y) -> y.getGrowth() - x.getGrowth()).collect(Collectors.toList());
                }
            } else {
                // 默认排序
                evaluateRatioDTOS = evaluateRatioDTOS.stream().sorted((x, y) -> y.getGrowth() - x.getGrowth()).collect(Collectors.toList());
            }
        }
        viewPointEvaluateDTO.setViewRatios(evaluateRatioDTOS);
        return ServiceResult.success(viewPointEvaluateDTO);
    }

    @Override
    public ServiceResult<EmotionDetailDTO> statEmotion(BaseChartParam baseChartParam) {
        String projectId = baseChartParam.getProjectId();
        List<String> fieldIds = analysisProjectDao.listTextFiledId(projectId, baseChartParam.getFieldId());
        PeriodTimeDTO curPeriodTime = baseChartParam.getTimeRangeDTO().getPeriodTime();
        // 获取上期开始结束时间
        PeriodTimeDTO lastPeriodTime = ChartUtil.getLastPeriodTime(baseChartParam.getTimeRangeDTO());

        EmotionDTO curEmotion = getEmotionPeriodData(projectId, fieldIds, curPeriodTime);
        EmotionDTO lastEmotion = getEmotionPeriodData(projectId, fieldIds, lastPeriodTime);
        // 计算情感增长值
        EmotionDTO growth = calculateEmotionGrowth(lastEmotion, curEmotion);
        // 计算情感增长率
        EmotionDTO growthRatio = calculateEmotionGrowthRatio(lastEmotion, curEmotion);
        // 组织返回参数
        EmotionDetailDTO emotionDetailDTO = new EmotionDetailDTO(curPeriodTime, lastPeriodTime, curEmotion, lastEmotion, growth, growthRatio);

        return ServiceResult.success(emotionDetailDTO);
    }

    @Override
    public ServiceResult<List<EmotionDTO>> statEmotionTrend(BaseChartParam baseChartParam) {
        List<EmotionDTO> emotionDTOS = new ArrayList<>();
        String projectId = baseChartParam.getProjectId();
        List<String> fieldIds = analysisProjectDao.listTextFiledId(projectId, baseChartParam.getFieldId());
        // 获取原话记录的最早时间
        LocalDateTime minRecordTime = recordDao.getMinRecordTimeByProject(projectId);
        // 没有原话记录的最小时间，就说明没有原话记录
        if (Validator.isNull(minRecordTime)) {
            return ServiceResult.success(emotionDTOS);
        }
        // 如果原话记录的最小时间大于周期的结束时间，返回
        if (minRecordTime.isAfter(baseChartParam.getTimeRangeDTO().getPeriodTime().getEndTime())) {
            return ServiceResult.success(emotionDTOS);
        }
        PeriodTimeDTO periodTimeDTO = baseChartParam.getTimeRangeDTO().getPeriodTime();
        // 根据周期数量计算时间周期
        Integer interval = baseChartParam.getTimeRangeDTO().getInterval();
        // 根据interval获取展示的周期数量
        Integer periodCount = getPeriodCount(interval);
        for (int i = 0; i < periodCount; i++) {
            EmotionDTO emotionPeriodData = getEmotionPeriodData(projectId, fieldIds, periodTimeDTO);
            emotionPeriodData.setPeriodTime(periodTimeDTO);
            // 计算顺序是从最末期到第一期，所以新计算的值添加到第一个
            emotionDTOS.add(0, emotionPeriodData);
            // 此处periodTimeDTO替换为上期时间
            periodTimeDTO = ChartUtil.getLastPeriodTime(periodTimeDTO, interval);
            if (periodTimeDTO.getEndTime().isBefore(minRecordTime)) {
                break;
            }
        }
        return ServiceResult.success(emotionDTOS);
    }

    @Override
    public ServiceResult<List<WorkCloudDataDTO>> statKeyword(WorkCloudParam workCloudParam) {
        List<WorkCloudDataDTO> workCloudDataDTOS = new ArrayList<>();
        String projectId = workCloudParam.getProjectId();
        String topicId = workCloudParam.getTopicId();
        // 获取话题下的关键字
        Set<String> topicKeywordSet = listTopicKeywords(projectId, topicId);
        // 获取行业下关键字。如果传入了话题id，就不需要查询行业关键字
        Set<String> industryKeywordSet = listIndustryKeywords(projectId, topicId);
        // 获取隐藏的关键字
        List<String> hiddenKeywords = hiddenKeywordDao.listHiddenKeywords(projectId);
        // 符合条件的关键字, 话题关键字+行业关键字-隐藏关键字
        topicKeywordSet.addAll(industryKeywordSet);
        topicKeywordSet.removeAll(hiddenKeywords);
        if (topicKeywordSet.isEmpty()) {
            return ServiceResult.success(workCloudDataDTOS);
        }
        List<String> keywords = new ArrayList<>(topicKeywordSet);
        List<String> fieldIds = analysisProjectDao.listTextFiledId(projectId, workCloudParam.getFieldId());
        // 查询原话中出现关键词的原话内容
        RecordSearchDTO recordSearchDTO = getRecordSearchDTO(projectId, fieldIds, workCloudParam.getTimeRangeDTO().getPeriodTime());
        recordSearchDTO.setKeywords(keywords);
        List<String> recordContentList = recordDao.listRecordContent(recordSearchDTO);
        // 没有原话记录，
        if (recordContentList.isEmpty()) {
            return ServiceResult.success(workCloudDataDTOS);
        }
        for (String keyword : keywords) {
            WorkCloudDataDTO workCloudDataDTO = new WorkCloudDataDTO();
            // 查看原话记录中包含关键词的数量
            long count = recordContentList.stream().filter(item -> item.contains(keyword)).count();
            workCloudDataDTO.setKeyword(keyword);
            workCloudDataDTO.setCount((int) count);
            workCloudDataDTOS.add(workCloudDataDTO);
        }
        // 根据过滤掉数量为的结果，根据数量进行排序，返回前50个关键词
        List<WorkCloudDataDTO> result = workCloudDataDTOS.stream().filter(item -> item.getCount() > 0).sorted((x, y) -> y.getCount() - x.getCount()).limit(WORD_CLOUD_RESULT_LIMIT).collect(Collectors.toList());
        return ServiceResult.success(result);
    }


    @Override
    public ServiceResult<TopicReferenceRateDTO> statTopicReference(TopicReferenceParam topicReferenceParam) {
        String projectId = topicReferenceParam.getProjectId();
        List<String> fieldIds = analysisProjectDao.listTextFiledId(projectId, topicReferenceParam.getFieldId());
        PeriodTimeDTO curPeriodTime = topicReferenceParam.getTimeRangeDTO().getPeriodTime();
        // 获取上期开始结束时间
        PeriodTimeDTO lastPeriodTime = ChartUtil.getLastPeriodTime(topicReferenceParam.getTimeRangeDTO());

        // 查询话题id对应的关键字（话题组对应的为话题组下话题的所有关键字）
        List<TopicDO> topicDOS = listTopicIdKeys(projectId, topicReferenceParam.getTopicGid(), topicReferenceParam.getTopicType());

        // 获取本期话题数据
        List<StatDTO> curStatDTO = getTopicReferenceData(projectId, fieldIds, curPeriodTime, topicDOS);
        List<StatDTO> lastStatDTO = getTopicReferenceData(projectId, fieldIds, lastPeriodTime, topicDOS);
        // 把上期数据合并到本期数据中
        List<ReferenceRateDTO> referenceRateDTOS = mergeReferenceData(curStatDTO, lastStatDTO);
        TopicReferenceRateDTO topicReferenceRateDTO = new TopicReferenceRateDTO();
        topicReferenceRateDTO.setCurPeriod(curPeriodTime);
        topicReferenceRateDTO.setLastPeriod(lastPeriodTime);
        // 对结果进行排序
        if (!referenceRateDTOS.isEmpty()) {
            if (Validator.isNotEmpty(topicReferenceParam.getSortKey())) {
                if (SortKeyEnum.SortKey.PREVIOUS.name().equals(topicReferenceParam.getSortKey())) {
                    referenceRateDTOS = referenceRateDTOS.stream().sorted((x, y) -> y.getLastRatio().compareTo(x.getLastRatio())).collect(Collectors.toList());
                } else if (SortKeyEnum.SortKey.CURRENT.name().equals(topicReferenceParam.getSortKey())) {
                    referenceRateDTOS = referenceRateDTOS.stream().sorted((x, y) -> y.getCurRatio().compareTo(x.getCurRatio())).collect(Collectors.toList());
                } else if (SortKeyEnum.SortKey.COMPARE_PREVIOUS.name().equals(topicReferenceParam.getSortKey())) {
                    referenceRateDTOS = referenceRateDTOS.stream().sorted((x, y) -> y.getGrowthRatio().compareTo(x.getGrowthRatio())).collect(Collectors.toList());
                }
            } else {
                // 默认排序
                referenceRateDTOS = referenceRateDTOS.stream().sorted((x, y) -> y.getGrowthRatio().compareTo(x.getGrowthRatio())).collect(Collectors.toList());
            }
        }
        topicReferenceRateDTO.setRefRates(referenceRateDTOS);
        return ServiceResult.success(topicReferenceRateDTO);
    }

    @Override
    public ServiceResult<TopicAnalysisDTO> statTopicAnalysis(PeriodChartParam periodChartParam) {
        String projectId = periodChartParam.getProjectId();
        List<String> fieldIds = analysisProjectDao.listTextFiledId(projectId, periodChartParam.getFieldId());
        PeriodTimeDTO curPeriodTime = periodChartParam.getTimeRangeDTO().getPeriodTime();
        // 获取上期开始结束时间
        PeriodTimeDTO lastPeriodTime = ChartUtil.getLastPeriodTime(periodChartParam.getTimeRangeDTO());

        // 查询话题id对应的关键字（话题组对应的为话题组下话题的所有关键字）
        List<TopicDO> topics = listTopicIdKeys(projectId, null, TopicTypeEnum.TopicType.TOPIC_VALUE);

        // 获取本期话题数据
        List<StatDTO> curStatDTOList = getTopicReferenceData(projectId, fieldIds, curPeriodTime, topics);
        List<StatDTO> lastStatDTOList = getTopicReferenceData(projectId, fieldIds, lastPeriodTime, topics);
        // 计算top5
        List<TopicTopDTO> topicTopDTOList = new ArrayList<>();
        List<StatDTO> needStatDTO;
        if (periodChartParam.getPeriod() == PeriodTypeEnum.CURRENT.getIndex()) {
            needStatDTO = curStatDTOList;
        } else {
            needStatDTO = lastStatDTOList;
        }
        needStatDTO.stream()
                .filter(item -> item.getRatio().compareTo(BigDecimal.ZERO) > 0).sorted((x, y) -> y.getRatio().compareTo(x.getRatio()))
                .limit(TOPIC_ANALYSIS_RESULT_COUNT)
                .forEach(item -> topicTopDTOList.add(new TopicTopDTO(item.getName(), item.getRatio())));
        // 计算本期最热门话题
        Optional<StatDTO> currentHotTopic = curStatDTOList.stream().min((x, y) -> y.getCount() - x.getCount());
        TopicHotDTO topicHotDTO = new TopicHotDTO();
        if (currentHotTopic.isPresent()) {
            StatDTO statDTO = currentHotTopic.get();
            topicHotDTO.setName(statDTO.getName());
            topicHotDTO.setCurCount(statDTO.getCount());
            Optional<StatDTO> lastHotTopic = lastStatDTOList.stream().filter(item -> item.getName().equals(statDTO.getName())).findFirst();
            int lastHotTopicCount = lastHotTopic.isPresent() ? lastHotTopic.get().getCount() : 0;
            topicHotDTO.setLastCount(lastHotTopicCount);
            topicHotDTO.setRingRatio(ChartUtil.calEmotionRatio2(statDTO.getCount(), lastHotTopicCount));
        }
        // 进度激增话题
        List<TopicHotDTO> topicFastDTOList = new ArrayList<>();
        for (int i = 0; i < curStatDTOList.size(); i++) {
            TopicHotDTO topicFastDTO = new TopicHotDTO();
            StatDTO curStatDTO = curStatDTOList.get(i);
            StatDTO lastStatDTO = lastStatDTOList.get(i);
            topicFastDTO.setName(curStatDTO.getName());
            topicFastDTO.setCurCount(curStatDTO.getCount());
            topicFastDTO.setLastCount(lastStatDTO.getCount());
            topicFastDTO.setRingRatio(ChartUtil.calEmotionRatio2(curStatDTO.getCount(), lastStatDTO.getCount()));
            topicFastDTOList.add(topicFastDTO);
        }
        Optional<TopicHotDTO> currentHotFastTopic = topicFastDTOList.stream().max(Comparator.comparing(TopicHotDTO::getRingRatio));
        Optional<TopicHotDTO> lastHotTopic = topicFastDTOList.stream().max(Comparator.comparingInt(TopicHotDTO::getLastCount));
        // 组织数据
        TopicAnalysisDTO topicAnalysisDTO = new TopicAnalysisDTO();
        topicAnalysisDTO.setTopicTopDTOList(topicTopDTOList);
        topicAnalysisDTO.setTopicFastDTO(currentHotFastTopic.orElseGet(TopicHotDTO::new));
        topicAnalysisDTO.setTopicHotLastDTO(lastHotTopic.orElseGet(TopicHotDTO::new));
        topicAnalysisDTO.setTopicHotDTO(topicHotDTO);
        return ServiceResult.success(topicAnalysisDTO);
    }

    @Override
    public ServiceResult<List<TopicRelationDTO>> statTopicRelation(TopicRelationParam topicRelationParam) {
        String topicId = topicRelationParam.getTopicId();
        String projectId = topicRelationParam.getProjectId();
        List<String> fieldIds = analysisProjectDao.listTextFiledId(projectId, topicRelationParam.getFieldId());
        PeriodTimeDTO periodTime = topicRelationParam.getTimeRangeDTO().getPeriodTime();
        RecordSearchDTO recordCommonSearchDTO = getRecordSearchDTO(projectId, fieldIds, periodTime);
        // 查询原话记录数量
        int recordCount = recordDao.countRecord(recordCommonSearchDTO).intValue();
        if (recordCount == 0) {
            ServiceResult.success(new ArrayList<>());
        }
        List<TopicDO> topics = topicDao.listTopicByProjectId(projectId);
        // 话题id对应的原话id数组
        Map<String, Set<String>> topicIdRecordIdMap = new HashMap<>(DEFAULT_MAP_INITIAL_CAPACITY);
        // 话题对应的id、name
        Map<String, String> topicIdName = new HashMap<>(DEFAULT_MAP_INITIAL_CAPACITY);
        // 过滤掉没有关键词的话题
        topics = topics.stream().filter(item -> Validator.isNotNull(item.getKeyDict()) && !item.getKeyDict().isEmpty()).collect(Collectors.toList());
        if (topics.isEmpty()) {
            ServiceResult.success(new ArrayList<>());
        }
        // 话题id。排列组合需要
        String[] topicIds = new String[topics.size()];
        // 查询话题对应的原话Id
        // for循环中进行查询，得到对应的原话id，下面匹配时根据原话id取交集
        for (int i = 0; i < topics.size(); i++) {
            TopicDO topic = topics.get(i);
            recordCommonSearchDTO.setKeywords(topic.getKeyDict());
            // 查询原话对应的id
            topicIdRecordIdMap.put(topic.getId(), recordDao.listRecordIdByKeywords(recordCommonSearchDTO));
            topicIdName.put(topic.getId(), topic.getName());
            topicIds[i] = topic.getId();
        }
        List<TopicRelationDTO> topicRelationDTOS = new ArrayList<>();
        // 话题id排列组合结果。从列表中选择 n 个组合
        List<String[]> topicCombin;
        if (Validator.isNotEmpty(topicId)) {
            topicCombin = new ArrayList<>();
            for (String id : topicIds) {
                if (!topicId.equals(id)) {
                    topicCombin.add(new String[]{topicId, id});
                }
            }
        } else {
            topicCombin = MathUtil.combinationSelect(topicIds, 2);
        }
        for (String[] topicCombineIds : topicCombin) {
            String primaryId = topicCombineIds[0];
            String secondId = topicCombineIds[1];
            TopicRelationDTO topicRelationDTO = new TopicRelationDTO();
            topicRelationDTO.setName(topicIdName.get(primaryId) + TOPIC_SEPARATOR + topicIdName.get(secondId));
            topicRelationDTO.setTotal(recordCount);
            // 把list转为set，利用set的retainAll方法取交集的数量。
            // 此处必须new HashSet，因为topicIdRecordIdMap取出的set经过retainAll后会影响原map中的数据
            Set<String> primaryRecordIds = new HashSet<>(topicIdRecordIdMap.getOrDefault(primaryId, new HashSet<>()));
            primaryRecordIds.retainAll(topicIdRecordIdMap.get(secondId));
            topicRelationDTO.setCount(primaryRecordIds.size());
            topicRelationDTO.setRatio(ChartUtil.calPercentage(primaryRecordIds.size(), recordCount));
            topicRelationDTOS.add(topicRelationDTO);
        }
        List<TopicRelationDTO> result = topicRelationDTOS.stream().filter(item -> item.getRatio() > 0).sorted((x, y) -> y.getRatio() - x.getRatio()).limit(TOPIC_RELATION_RESULT_COUNT).collect(Collectors.toList());
        return ServiceResult.success(result);
    }

    @Override
    public ServiceResult<List<TopicStatDTO>> statTopicReferenceTrend(TopicReferenceTrendParam topicReferenceTrendParam) {
        List<TopicStatDTO> topicStatDTOS = new ArrayList<>();
        String projectId = topicReferenceTrendParam.getProjectId();
        List<String> fieldIds = analysisProjectDao.listTextFiledId(projectId, topicReferenceTrendParam.getFieldId());
        // 获取原话记录的最早时间
        LocalDateTime minRecordTime = recordDao.getMinRecordTimeByProject(projectId);
        // 没有原话记录的最小时间，就说明没有原话记录
        if (Validator.isNull(minRecordTime)) {
            return ServiceResult.success(topicStatDTOS);
        }
        // 如果原话记录的最小时间大于周期的结束时间，返回
        if (minRecordTime.isAfter(topicReferenceTrendParam.getTimeRangeDTO().getPeriodTime().getEndTime())) {
            return ServiceResult.success(topicStatDTOS);
        }
        // 符合条件的时间周期函数。默认添加本期
        List<PeriodTimeDTO> periodTimeDTOS = new LinkedList<>();
        periodTimeDTOS.add(topicReferenceTrendParam.getTimeRangeDTO().getPeriodTime());
        // 根据周期数量计算时间周期
        Integer interval = topicReferenceTrendParam.getTimeRangeDTO().getInterval();
        // 根据interval获取展示的周期数量
        Integer periodCount = getPeriodCount(interval);
        for (int i = 1; i < periodCount; i++) {
            PeriodTimeDTO lastPeriodTime = ChartUtil.getLastPeriodTime(periodTimeDTOS.get(i - 1), interval);
            if (lastPeriodTime.getEndTime().isBefore(minRecordTime)) {
                break;
            }
            periodTimeDTOS.add(lastPeriodTime);
        }

        // 需要统计的话题列表
        List<TopicDO> topicList = new ArrayList<>();
        // 查询需要分析的话题
        if (Validator.isNotEmpty(topicReferenceTrendParam.getTopicGid())) {
            topicList = topicDao.listTopicByGid(topicReferenceTrendParam.getTopicGid());
        } else if (Validator.isNotEmpty(topicReferenceTrendParam.getTopicId())) {
            topicList.add(topicDao.getById(topicReferenceTrendParam.getTopicId()));
        }
        // 查询话题下的所有关键字
        List<String> keywords = new ArrayList<>();
        for (TopicDO topic : topicList) {
            if (Validator.isNotNull(topic) && Validator.isNotNull(topic.getKeyDict())) {
                keywords.addAll(topic.getKeyDict());
            }
        }
        // 周期添加时是从最后一起到第一期，所以需要倒序遍历
        for (int i = periodTimeDTOS.size() - 1; i >= 0; i--) {
            PeriodTimeDTO periodTimeDTO = periodTimeDTOS.get(i);
            // 统计话题的周期数据
            TopicStatDTO topicPeriodData = statTopicPeriodData(projectId, fieldIds, periodTimeDTO, keywords);
            topicPeriodData.setPeriodTime(periodTimeDTO);
            topicStatDTOS.add(topicPeriodData);
        }
        return ServiceResult.success(topicStatDTOS);
    }

    @Override
    public ServiceResult<List<ViewpointEvaluateStatDTO>> statViewpointEvaluateTrend(ViewpointEvaluateTrendParam topicReferenceTrendParam) {
        List<ViewpointEvaluateStatDTO> viewpointEvaluateStatDTOS = new ArrayList<>();
        String projectId = topicReferenceTrendParam.getProjectId();
        // 需要统计的观点或观点组id, 调用时有限制条件。观点或观点组id必有一个
        String statId = "";
        int statType = ViewpointTypeEnum.ViewpointType.VIEW_VALUE;
        if (Validator.isNotEmpty(topicReferenceTrendParam.getViewGid())) {
            statId = topicReferenceTrendParam.getViewGid();
            statType = ViewpointTypeEnum.ViewpointType.GROUP_VALUE;
            ViewpointDO viewpoint = viewPointDao.getById(statId);
            if (Validator.isNull(viewpoint) || viewpoint.getType() == ViewpointTypeEnum.ViewpointType.VIEW_VALUE) {
                return ServiceResult.success(viewpointEvaluateStatDTOS);
            }
        } else if (Validator.isNotEmpty(topicReferenceTrendParam.getViewId())) {
            statId = topicReferenceTrendParam.getViewId();
            statType = ViewpointTypeEnum.ViewpointType.VIEW_VALUE;
            ViewpointDO viewpoint = viewPointDao.getById(statId);
            if (Validator.isNull(viewpoint) || viewpoint.getType() == ViewpointTypeEnum.ViewpointType.GROUP_VALUE) {
                return ServiceResult.success(viewpointEvaluateStatDTOS);
            }
        }
        // 获取原话记录的最早时间
        LocalDateTime minRecordTime = recordDao.getMinRecordTimeByProject(projectId);
        // 没有原话记录的最小时间，就说明没有原话记录
        if (Validator.isNull(minRecordTime)) {
            return ServiceResult.success(viewpointEvaluateStatDTOS);
        }
        // 如果原话记录的最小时间大于周期的结束时间，返回
        if (minRecordTime.isAfter(topicReferenceTrendParam.getTimeRangeDTO().getPeriodTime().getEndTime())) {
            return ServiceResult.success(viewpointEvaluateStatDTOS);
        }
        // 符合条件的时间周期函数。默认添加本期
        List<PeriodTimeDTO> periodTimeDTOS = new LinkedList<>();
        periodTimeDTOS.add(topicReferenceTrendParam.getTimeRangeDTO().getPeriodTime());
        // 根据周期数量计算时间周期
        Integer interval = topicReferenceTrendParam.getTimeRangeDTO().getInterval();
        // 固定显示7天的周期
        for (int i = 1; i < VIEW_EVALUATE_PERIOD_COUNT; i++) {
            PeriodTimeDTO lastPeriodTime = ChartUtil.getLastPeriodTime(periodTimeDTOS.get(i - 1), interval);
            if (lastPeriodTime.getEndTime().isBefore(minRecordTime)) {
                break;
            }
            periodTimeDTOS.add(lastPeriodTime);
        }
        // 需要统计的观点
        List<ViewpointDO> viewPoints = viewPointDao.listViewpoints(projectId);
        // 根据观点组和观点。把结果id组织起来。String为观点组id，List中为观点id
        Map<String, Set<String>> viewPointRelation = viewPoints.stream().filter(item -> Validator.isNotEmpty(item.getGroupId())).collect(Collectors.groupingBy(ViewpointDO::getGroupId, Collectors.mapping(ViewpointDO::getId, Collectors.toSet())));
        List<String> fieldIds = analysisProjectDao.listTextFiledId(projectId, topicReferenceTrendParam.getFieldId());
        // 周期添加时是从最后一起到第一期，所以需要倒序遍历
        for (int i = periodTimeDTOS.size() - 1; i >= 0; i--) {
            PeriodTimeDTO periodTimeDTO = periodTimeDTOS.get(i);
            ViewpointEvaluateStatDTO viewpointEvaluateStatDTO = statViewEvaluateCount(projectId, fieldIds, periodTimeDTO, statId, statType, viewPointRelation);
            viewpointEvaluateStatDTO.setPeriodTime(periodTimeDTO);
            viewpointEvaluateStatDTOS.add(viewpointEvaluateStatDTO);
        }
        return ServiceResult.success(viewpointEvaluateStatDTOS);
    }

    /**
     * 统计观点数量
     *
     * @param projectId         项目id
     * @param fieldIds          字段id
     * @param curPeriodTime     周期时间
     * @param viewPointRelation 观点关系
     * @return ViewpointEvaluateStatDTO 话题统计数据
     * @author jy.zhao
     * @date 2021/7/27 21:17
     **/
    private ViewpointEvaluateStatDTO statViewEvaluateCount(String projectId, List<String> fieldIds, PeriodTimeDTO curPeriodTime, String viewId, Integer statType, Map<String, Set<String>> viewPointRelation) {
        ViewpointEvaluateStatDTO viewpointEvaluateStatDTO = new ViewpointEvaluateStatDTO();
        // 查询原话记录Id
        RecordSearchDTO recordSearchDTO = getRecordSearchDTO(projectId, fieldIds, curPeriodTime);
        List<String> recordIds = recordDao.listRecordIds(recordSearchDTO);
        int recordCount = recordIds.size();
        // 观点统计，统计结果String为观点id，Map为统计结果
        // 不能直接判断原话数量就返回空数组，最终要返回观点的名称等内容
        if (recordCount == 0) {
            return viewpointEvaluateStatDTO;
        }

        Map<String, Integer> viewStat = new HashMap<>(DEFAULT_MAP_SIZE);
        viewStat.put(RECORD_SENTI_POSITIVE_KEY, 0);
        viewStat.put(RECORD_SENTI_MIX_KEY, 0);
        viewStat.put(RECORD_SENTI_NEUTRAL_KEY, 0);
        viewStat.put(RECORD_SENTI_NEGATIVE_KEY, 0);
        viewStat.put(RECORD_SENTI_TOTAL_NUM_KEY, 0);
        Set<String> viewChildrenIds = viewPointRelation.get(viewId);
        List<JoinInfoViewpointDO> joinInfoViewpointDOList = joinInfoDao.listJoinInfos(projectId, recordIds);
        Map<String, List<JoinInfoViewpointDO>> JoinInfoMap = joinInfoViewpointDOList.stream().collect(Collectors.groupingBy(JoinInfoViewpointDO::getRecordId, Collectors.mapping(item -> item, Collectors.toList())));
        // 统计观点出现的次数
        for (Map.Entry<String, List<JoinInfoViewpointDO>> entry : JoinInfoMap.entrySet()) {
            List<JoinInfoViewpointDO> viewPointList = entry.getValue();
            if (statType == ViewpointTypeEnum.ViewpointType.VIEW_VALUE) {
                // 子观点情感统计
                for (JoinInfoViewpointDO viewPoint : viewPointList) {
                    // 累加viewPoint中观点id对应的总数量
                    if (viewPoint.getViewId().equals(viewId)) {
                        viewStat.replace(RECORD_SENTI_TOTAL_NUM_KEY, viewStat.get(RECORD_SENTI_TOTAL_NUM_KEY) + 1);
                        // 累加viewPoint中观点id对应的情感的数量
                        String sentiKey = String.valueOf(viewPoint.getViewSenti());
                        viewStat.replace(sentiKey, viewStat.get(sentiKey) + 1);
                    }
                }
            } else {
                // 观点组情感标记 0代表负向， 1代表中性 2代表正向
                int[] sentiMark = new int[3];
                for (JoinInfoViewpointDO viewPoint : viewPointList) {
                    // entry.getValue()为观点组id对应的子观点id
                    if (viewChildrenIds.contains(viewPoint.getViewId())) {
                        // 此处使用数组下标对应情感来进行数量统计。由于观点情感值【-1， 2】，所以加1.
                        // 使用数组下标对应的数字来判断原话中的观点情感是否出现。 0代表没有出现，1代表出现
                        sentiMark[viewPoint.getViewSenti() + 1] = 1;
                    }
                }
                // 如果既有正向又有负向，就标记为混合
                if (sentiMark[0] > 0 && sentiMark[2] > 0) {
                    viewStat.replace(RECORD_SENTI_MIX_KEY, viewStat.get(RECORD_SENTI_MIX_KEY) + 1);
                }
                // 出现正向情感
                if (sentiMark[0] == 0 && sentiMark[2] > 0) {
                    viewStat.replace(RECORD_SENTI_POSITIVE_KEY, viewStat.get(RECORD_SENTI_POSITIVE_KEY) + sentiMark[2]);
                }
                // 出现中性情感
                viewStat.replace(RECORD_SENTI_NEUTRAL_KEY, viewStat.get(RECORD_SENTI_NEUTRAL_KEY) + sentiMark[1]);
                // 出现负向情感
                if (sentiMark[0] > 0 && sentiMark[2] == 0) {
                    viewStat.replace(RECORD_SENTI_NEGATIVE_KEY, viewStat.get(RECORD_SENTI_NEGATIVE_KEY) + sentiMark[0]);
                }
                int totalMark = sentiMark[0] + sentiMark[1] + sentiMark[2];
                if (totalMark > 0) {
                    viewStat.replace(RECORD_SENTI_TOTAL_NUM_KEY, viewStat.get(RECORD_SENTI_TOTAL_NUM_KEY) + 1);
                }
            }
        }
        viewpointEvaluateStatDTO.setNegative(viewStat.get(RECORD_SENTI_NEGATIVE_KEY));
        viewpointEvaluateStatDTO.setNeutral(viewStat.get(RECORD_SENTI_NEUTRAL_KEY));
        viewpointEvaluateStatDTO.setMix(viewStat.get(RECORD_SENTI_MIX_KEY));
        viewpointEvaluateStatDTO.setPositive(viewStat.get(RECORD_SENTI_POSITIVE_KEY));
        return viewpointEvaluateStatDTO;
    }

    /**
     * 统计话题周期数据
     *
     * @param projectId     项目id
     * @param fieldIds      字段id
     * @param curPeriodTime 周期时间
     * @return TopicStatDTO 话题统计数据
     * @author jy.zhao
     * @date 2021/7/27 18:59
     **/
    private TopicStatDTO statTopicPeriodData(String projectId, List<String> fieldIds, PeriodTimeDTO curPeriodTime, List<String> keywords) {
        RecordSearchDTO recordSearchDTO = getRecordSearchDTO(projectId, fieldIds, curPeriodTime);
        // 获取原话数量
        int recordCount = recordDao.countRecord(recordSearchDTO).intValue();
        TopicStatDTO topicStatDTO = new TopicStatDTO();
        if (recordCount == 0 || keywords.isEmpty()) {
            return topicStatDTO;
        }
        recordSearchDTO.setKeywords(keywords);
        // 查询关键字匹配的数量
        int count = recordDao.countRecordByKeyword(recordSearchDTO).intValue();
        // 封装topicStatDTO
        topicStatDTO.setCount(count);
        topicStatDTO.setTotal(recordCount);
        topicStatDTO.setRatio(ChartUtil.calPercentage(count, recordCount));
        return topicStatDTO;
    }

    private Map<String, Map<String, Integer>> statChildViewpoint(String projectId, List<String> recordIds) {
        Map<String, Map<String, Integer>> viewStatMap = new HashMap<>(DEFAULT_MAP_SIZE);
        // 根据原话id查找对应的所有joinInfo的数据（原话中没有情感，joininfo中有）
        List<JoinInfoViewpointDO> viewPointList = joinInfoDao.listJoinInfos(projectId, recordIds);
        // 子观点情感统计
        for (JoinInfoViewpointDO viewPoint : viewPointList) {
            // 根据观点id获取对应的统计结果，如果没有，则创建默认的统计结果，
            Map<String, Integer> viewStat = getOrCreate(viewStatMap, viewPoint.getViewId());
            // 累加viewPoint中观点id对应的总数量
            viewStat.replace(RECORD_SENTI_TOTAL_NUM_KEY, viewStat.get(RECORD_SENTI_TOTAL_NUM_KEY) + 1);
            // 累加viewPoint中观点id对应的情感的数量
            String sentiKey = String.valueOf(viewPoint.getViewSenti());
            viewStat.replace(sentiKey, viewStat.get(sentiKey) + 1);
        }
        return viewStatMap;
    }

    /**
     * 把上期数据合并到本期数据中
     *
     * @param curStatDTOs  本期数据
     * @param lastStatDTOs 上期数据
     * @return List<ReferenceRateDTO>
     * @author jy.zhao
     * @date 2021/5/17 11:45
     **/
    private List<ReferenceRateDTO> mergeReferenceData(List<StatDTO> curStatDTOs, List<StatDTO> lastStatDTOs) {
        List<ReferenceRateDTO> referenceRateDTOS = new ArrayList<>();
        for (int i = 0; i < curStatDTOs.size(); i++) {
            ReferenceRateDTO referenceRateDTO = new ReferenceRateDTO();
            StatDTO curStatDTO = curStatDTOs.get(i);
            StatDTO lastStatDTO = lastStatDTOs.get(i);
            referenceRateDTO.setId(curStatDTO.getId());
            referenceRateDTO.setName(curStatDTO.getName());
            referenceRateDTO.setCurNum(curStatDTO.getCount());
            referenceRateDTO.setCurTotalNum(curStatDTO.getTotal());
            referenceRateDTO.setCurRatio(curStatDTO.getRatio());
            referenceRateDTO.setLastNum(lastStatDTO.getCount());
            referenceRateDTO.setLastTotalNum(lastStatDTO.getTotal());
            referenceRateDTO.setLastRatio(lastStatDTO.getRatio());
            referenceRateDTO.setGrowthRatio(referenceRateDTO.getCurRatio().subtract(referenceRateDTO.getLastRatio()));
            referenceRateDTOS.add(referenceRateDTO);
        }
        return referenceRateDTOS;
    }

    /**
     * 获取话题引用数据
     *
     * @param projectId     项目id
     * @param fieldIds      属性id
     * @param periodTime    时间
     * @param topicEntities 话题
     * @return List<StatDTO>
     * @author jy.zhao
     * @date 2021/5/17 14:17
     **/
    private List<StatDTO> getTopicReferenceData(String projectId, List<String> fieldIds, PeriodTimeDTO periodTime, List<TopicDO> topicEntities) {
        List<StatDTO> statDTOS = new ArrayList<>();
        // 获取原话数量
        RecordSearchDTO recordSearchDTO = getRecordSearchDTO(projectId, fieldIds, periodTime);
        List<String> recordContent = recordDao.listRecordContent(recordSearchDTO);
        int recordCount = recordContent.size();
        for (TopicDO topic : topicEntities) {
            StatDTO statDTO = new StatDTO();
            statDTO.setId(topic.getId());
            statDTO.setName(topic.getName());
            statDTO.setTotal(recordCount);
            if (recordCount > 0 && Validator.isNotNull(topic.getKeyDict()) && !topic.getKeyDict().isEmpty()) {
//                // 根据上面的条件，添加关键词，进行查询，查询结果为话题下关键词对应的原话的数量
//                recordSearchDTO.setKeywords(topic.getKeyDict());
//                int count = recordDao.countRecordByKeyword(recordSearchDTO).intValue();
//                statDTO.setCount(count);
                int count = 0;
                for (String record : recordContent) {
                    for (String keyword : topic.getKeyDict()) {
                        if (record.contains(keyword)) {
                            count++;
                            // 一条原话只与话题匹配一次
                            break;
                        }
                    }
                }
                statDTO.setCount(count);
                statDTO.setRatio(ChartUtil.calPercentage2(count, recordCount));
            }
            statDTOS.add(statDTO);
        }
        return statDTOS;
    }

    /**
     * 查询话题id对应的关键字。
     *
     * @param projectId 项目id
     * @param topicGid  话题组id
     * @param topicType 话题类型
     * @return Map<String, List < String>>
     * @author jy.zhao
     * @date 2021/5/17 10:49
     **/
    private List<TopicDO> listTopicIdKeys(String projectId, String topicGid, Integer topicType) {
        // 查询需要分析的话题
        if (Validator.isNotEmpty(topicGid)) {
            return topicDao.listTopicByGid(topicGid);
        }
        if (topicType == TopicTypeEnum.TopicType.TOPIC_VALUE) {
            return topicDao.listTopicByType(projectId, TopicTypeEnum.TopicType.TOPIC_VALUE);
        }
        // 处理话题组。找到话题组对应的关键字
        List<TopicDO> topicEntities = topicDao.listTopicByProjectId(projectId);

        List<TopicDO> topicGroupList = new ArrayList<>();
        Map<String, List<String>> topicIdKeyMap = new HashMap<>();
        for (TopicDO t : topicEntities) {
            if (t.getType() == TopicTypeEnum.TopicType.GROUP_VALUE) {
                topicGroupList.add(t);
                continue;
            }
            if (topicIdKeyMap.containsKey(t.getTopicGID())) {
                List<String> keyList = topicIdKeyMap.get(t.getTopicGID());
                keyList.addAll(t.getKeyDict());
                topicIdKeyMap.replace(t.getTopicGID(), keyList);
            } else {
                topicIdKeyMap.put(t.getTopicGID(), t.getKeyDict());
            }
        }
        List<TopicDO> result = new ArrayList<>();
        // 添加未分组话题组,未分组的话题属于话题，有对应的GID，
        for (TopicDO topicGroup : topicGroupList) {
            // 如果没有话题，就不需要展示话题组
            if (topicIdKeyMap.containsKey(topicGroup.getId())) {
                topicGroup.setKeyDict(topicIdKeyMap.get(topicGroup.getId()));
                result.add(topicGroup);
            }
        }
        return result;
    }

    /**
     * 获取话题下的关键字
     *
     * @param projectId 项目id
     * @param topicId   话题id
     * @return Set<String> 关键字列表
     * @author jy.zhao
     * @date 2021/5/14 15:43
     **/
    private Set<String> listTopicKeywords(String projectId, String topicId) {
        Set<String> keywords = new HashSet<>();
        if (Validator.isEmpty(topicId)) {
            return topicDao.listTopicKeyword(projectId);
        }
        TopicDO topic = topicDao.getById(topicId);
        if (Validator.isNotEmpty(topic) && Validator.isNotNull(topic.getKeyDict())) {
            keywords.addAll(topic.getKeyDict());
        }
        return keywords;
    }

    /**
     * 获取行业关键字
     *
     * @param projectId 项目id
     * @return Set<String>
     * @author jy.zhao
     * @date 2021/5/24 15:18
     **/
    private Set<String> listIndustryKeywords(String projectId, String topicId) {
        Set<String> keywords = new HashSet<>();
        if (Validator.isNotEmpty(topicId)) {
            return keywords;
        }
        // 获取行业下关键字
        ProjectDO project = analysisProjectDao.findById(projectId);
        if (Validator.isNull(project)) {
            return keywords;
        }
        return keywordService.listIndustryKeyword(project.getModelId());

    }

    /**
     * 计算情感增长率
     *
     * @param lastEmotion 上期数据
     * @param curEmotion  本期数据
     * @return EmotionDTO
     * @author jy.zhao
     * @date 2021/5/21 11:37
     **/
    private EmotionDTO calculateEmotionGrowthRatio(EmotionDTO lastEmotion, EmotionDTO curEmotion) {
        EmotionDTO emotionDTO = new EmotionDTO();
        emotionDTO.setNegative(ChartUtil.calEmotionRatio(curEmotion.getNegative(), lastEmotion.getNegative()));
        emotionDTO.setPositive(ChartUtil.calEmotionRatio(curEmotion.getPositive(), lastEmotion.getPositive()));
        emotionDTO.setNeutral(ChartUtil.calEmotionRatio(curEmotion.getNeutral(), lastEmotion.getNeutral()));
        emotionDTO.setMixture(ChartUtil.calEmotionRatio(curEmotion.getMixture(), lastEmotion.getMixture()));
        emotionDTO.setTotal(ChartUtil.calEmotionRatio(curEmotion.getTotal(), lastEmotion.getTotal()));
        return emotionDTO;
    }

    /**
     * 计算情感增长值
     *
     * @param lastEmotion 上期数据
     * @param curEmotion  本期数据
     * @return EmotionDTO
     * @author jy.zhao
     * @date 2021/5/21 11:36
     **/
    private EmotionDTO calculateEmotionGrowth(EmotionDTO lastEmotion, EmotionDTO curEmotion) {
        EmotionDTO emotionDTO = new EmotionDTO();
        emotionDTO.setNegative(curEmotion.getNegative() - lastEmotion.getNegative());
        emotionDTO.setPositive(curEmotion.getPositive() - lastEmotion.getPositive());
        emotionDTO.setNeutral(curEmotion.getNeutral() - lastEmotion.getNeutral());
        emotionDTO.setMixture(curEmotion.getMixture() - lastEmotion.getMixture());
        emotionDTO.setTotal(curEmotion.getTotal() - lastEmotion.getTotal());
        // 计算本期占比和本期占比
        lastEmotion.setPosRatio(ChartUtil.calPercentage(lastEmotion.getPositive(), lastEmotion.getTotal()));
        lastEmotion.setNegRatio(ChartUtil.calPercentage(lastEmotion.getNegative(), lastEmotion.getTotal()));
        curEmotion.setPosRatio(ChartUtil.calPercentage(curEmotion.getPositive(), curEmotion.getTotal()));
        curEmotion.setNegRatio(ChartUtil.calPercentage(curEmotion.getNegative(), curEmotion.getTotal()));
        emotionDTO.setPosRatio(curEmotion.getPosRatio() - lastEmotion.getPosRatio());
        emotionDTO.setNegRatio(curEmotion.getNegRatio() - lastEmotion.getNegRatio());
        return emotionDTO;
    }

    /**
     * 根据interval获取展示的周期数量
     *
     * @param interval 时间间隔枚举
     * @return Integer
     * @author jy.zhao
     * @date 2021/5/14 17:08
     **/
    private Integer getPeriodCount(Integer interval) {
        // 自定义时间和天显示都是31个周期
        int periodCount = INTERVAL_DAY_PERIOD_COUNT;
        // 周、月、季度默认显示12周期
        if (interval == IntervalEnum.Interval.WEEK_VALUE || interval == IntervalEnum.Interval.MONTH_VALUE || interval == IntervalEnum.Interval.QUARTER_VALUE) {
            periodCount = INTERVAL_MONTH_PERIOD_COUNT;
        } else if (interval == IntervalEnum.Interval.YEAR_VALUE) {
            // 年默认为10个周期
            periodCount = INTERVAL_YEAR_PERIOD_COUNT;
        }
        return periodCount;
    }

    /**
     * 获取情感周期数据
     *
     * @param projectId     项目id
     * @param fieldIds      字段id
     * @param curPeriodTime 周期时间
     * @return EmotionDTO 情感周期数据
     * @author jy.zhao
     * @date 2021/5/14 13:11
     **/
    private EmotionDTO getEmotionPeriodData(String projectId, List<String> fieldIds, PeriodTimeDTO curPeriodTime) {
        RecordSearchDTO recordSearchDTO = getRecordSearchDTO(projectId, fieldIds, curPeriodTime);
        // 对情感进行分组
        List<RecordSentiDTO> recordSentiDTOList = recordDao.listRecordSentisGroupBySenti(recordSearchDTO);
        EmotionDTO emotionDTO = new EmotionDTO();
        if (CollUtil.isEmpty(recordSentiDTOList)) {
            return emotionDTO;
        }
        for (RecordSentiDTO recordSentiDTO : recordSentiDTOList) {
            if (recordSentiDTO.getSenti() == RecordSentimentEnum.RecordSentiment.POSITIVE.getNumber()) {
                emotionDTO.setPositive(recordSentiDTO.getCount());
            } else if (recordSentiDTO.getSenti() == RecordSentimentEnum.RecordSentiment.NEGATIVE.getNumber()) {
                emotionDTO.setNegative(recordSentiDTO.getCount());
            } else if (recordSentiDTO.getSenti() == RecordSentimentEnum.RecordSentiment.NEUTRAL.getNumber()) {
                emotionDTO.setNeutral(recordSentiDTO.getCount());
            } else if (recordSentiDTO.getSenti() == RecordSentimentEnum.RecordSentiment.MIX.getNumber()) {
                emotionDTO.setMixture(recordSentiDTO.getCount());
            }
        }
        // 对分组后的数据进行处理，分组的情感就4种
        int total = emotionDTO.getPositive() + emotionDTO.getNegative() + emotionDTO.getNeutral() + emotionDTO.getMixture();
        emotionDTO.setTotal(total);
        return emotionDTO;
    }

    /**
     * merge本期、上期观点评价变化，
     *
     * @param curPeriodData  本期观点数据
     * @param lastPeriodData 上期观点数据
     * @return List<ViewPointEvaluateRadioDTO>
     * @author jy.zhao
     * @date 2021/5/13 20:46
     **/
    private List<ViewPointEvaluateRatioDTO> mergeViewpointEvaluateStatDTO(List<StatDTO> curPeriodData, List<StatDTO> lastPeriodData) {
        Map<String, String> viewShowAliasMap = ViewpointUtil.VIEW_SHOW_ALIAS_MAP;
        List<ViewPointEvaluateRatioDTO> ratioDTOs = new ArrayList<>();
        for (int i = 0; i < curPeriodData.size(); i++) {
            ViewPointEvaluateRatioDTO ratioDTO = new ViewPointEvaluateRatioDTO();
            StatDTO curStatDTO = curPeriodData.get(i);
            StatDTO lastStatDTO = lastPeriodData.get(i);
            ratioDTO.setId(curStatDTO.getId());
            ratioDTO.setName(viewShowAliasMap.getOrDefault(curStatDTO.getName(), curStatDTO.getName()));
            ratioDTO.setCurNum(curStatDTO.getCount());
            ratioDTO.setCurRatio(ChartUtil.calPercentage(curStatDTO.getCount(), curStatDTO.getTotal()));
            ratioDTO.setCurTotalNum(curStatDTO.getTotal());
            ratioDTO.setLastNum(lastStatDTO.getCount());
            ratioDTO.setLastTotalNum(lastStatDTO.getTotal());
            ratioDTO.setLastRatio(ChartUtil.calPercentage(lastStatDTO.getCount(), lastStatDTO.getTotal()));
            ratioDTO.setGrowth(ratioDTO.getCurRatio() - ratioDTO.getLastRatio());
            ratioDTOs.add(ratioDTO);
        }

        return ratioDTOs;
    }

    /**
     * 获取观点变化
     *
     * @param viewEvaluateParam 观点评价参数
     * @param viewPointToShow
     * @param periodTimeDTO     周期时间
     * @return List<StatDTO> 统计结果
     * @author jy.zhao
     * @date 2021/5/13 20:25
     **/
    private List<StatDTO> statViewEvaluatePeriodData(ViewpointEvaluateParam viewEvaluateParam, List<String> fieldIds, List<ViewpointDO> viewPointToShow, Map<String, Set<String>> viewPointRelation, PeriodTimeDTO periodTimeDTO) {
        String projectId = viewEvaluateParam.getProjectId();
        Integer evaluationType = viewEvaluateParam.getEvaluationType();
        if (Validator.isEmpty(evaluationType)) {
            evaluationType = EvaluationTypeEnum.EvaluationType.GOOD_VALUE;
        }
        // 查询原话记录Id
        RecordSearchDTO recordSearchDTO = getRecordSearchDTO(projectId, fieldIds, periodTimeDTO);
        List<String> recordIds = recordDao.listRecordIds(recordSearchDTO);
        int recordCount = recordIds.size();
        // 观点统计，统计结果String为观点id，Map为统计结果
        // 不能直接判断原话数量就返回空数组，最终要返回观点的名称等内容
        Map<String, Map<String, Integer>> viewPointStatMap = new HashMap<>(DEFAULT_MAP_SIZE);
        if (recordCount > 0) {
            viewPointStatMap = statViewpoint(projectId, viewPointRelation, recordIds);
        }

        List<StatDTO> viewpointStatDTOS = new ArrayList<>();
        // 遍历观点和观点组
        for (ViewpointDO viewPoint : viewPointToShow) {
            String viewPointId = viewPoint.getId();
            StatDTO statDTO = new StatDTO();
            statDTO.setId(viewPointId);
            statDTO.setName(viewPoint.getViewContent());
            if (recordCount > 0 && viewPointStatMap.containsKey(viewPointId)) {
                Map<String, Integer> viewPointStat = viewPointStatMap.get(viewPointId);
                int evaluateCount;
                if (evaluationType == EvaluationTypeEnum.EvaluationType.BAD_VALUE) {
                    evaluateCount = viewPointStat.get(RECORD_SENTI_NEGATIVE_KEY);
                } else {
                    evaluateCount = viewPointStat.get(RECORD_SENTI_POSITIVE_KEY);
                }
                statDTO.setCount(evaluateCount);
                statDTO.setTotal(recordCount);
            }
            viewpointStatDTOS.add(statDTO);
        }
        return viewpointStatDTOS;
    }

    /**
     * 获取查询原话记录的通用参数
     *
     * @param projectId     项目id
     * @param fieldId       字段id， 为null时查询项目下的所有fieldId
     * @param periodTimeDTO 时间周期
     * @return RecordSearchDTO
     * @author jy.zhao
     * @date 2021/5/13 19:33
     **/
    private RecordSearchDTO getCommonRecordSearchDTO(String projectId, String fieldId, PeriodTimeDTO periodTimeDTO) {
        RecordSearchDTO recordSearchDTO = new RecordSearchDTO();
        recordSearchDTO.setProjectId(projectId);
        recordSearchDTO.setPeriodTime(periodTimeDTO);
        List<String> fieldIds = analysisProjectDao.listTextFiledId(projectId, fieldId);
        recordSearchDTO.setFieldIds(fieldIds);
        return recordSearchDTO;
    }

    /**
     * 获取查询原话记录的通用参数
     *
     * @param projectId     项目id
     * @param fieldIds      字段ids
     * @param periodTimeDTO 时间周期
     * @return RecordSearchDTO
     * @author jy.zhao
     * @date 2021/11/10 14:31
     **/
    private RecordSearchDTO getRecordSearchDTO(String projectId, List<String> fieldIds, PeriodTimeDTO periodTimeDTO) {
        RecordSearchDTO recordSearchDTO = new RecordSearchDTO();
        recordSearchDTO.setProjectId(projectId);
        recordSearchDTO.setPeriodTime(periodTimeDTO);
        recordSearchDTO.setFieldIds(fieldIds);
        return recordSearchDTO;
    }

    /**
     * 格式化观点统计的结果为层级结构。 观点组-->观点
     *
     * @param recordCount        原话数量。计算提及率
     * @param viewPointStatMap   观点统计结果。
     * @param viewPointRelation  观点组和观点的上下级关系
     * @param viewPointIdNameMap 观点id对应的name
     * @return List<ViewpointGroupStatDTO>
     * @author jy.zhao
     * @date 2021/5/13 13:49
     **/
    private List<ViewpointGroupStatDTO> formatViewpointStatResult(Integer recordCount, Map<String, Map<String, Integer>> viewPointStatMap, Map<String, Set<String>> viewPointRelation, Map<String, String> viewPointIdNameMap) {
        List<ViewpointGroupStatDTO> viewPointGroupStats = new ArrayList<>();
        Map<String, String> viewShowAliasMap = ViewpointUtil.VIEW_SHOW_ALIAS_MAP;
        // 计算观点统计数据
        for (Map.Entry<String, Set<String>> entry : viewPointRelation.entrySet()) {
            String viewPointGroupId = entry.getKey();
            String viewPointGroupName = viewPointIdNameMap.get(viewPointGroupId);
            ViewpointStatDTO viewPointGroupStatDTO = getViewpointStatDTO(viewPointGroupId, viewPointGroupName, recordCount, viewPointStatMap);
            List<ViewpointStatDTO> viewPointStats = new ArrayList<>();
            for (String viewPointId : entry.getValue()) {
                String viewPointName = viewPointIdNameMap.get(viewPointId);
                // 观点名称替换
                ViewpointStatDTO viewPointStatDTO = getViewpointStatDTO(viewPointId, viewShowAliasMap.getOrDefault(viewPointName, viewPointName), recordCount, viewPointStatMap);
                viewPointStats.add(viewPointStatDTO);
            }
            viewPointGroupStats.add(new ViewpointGroupStatDTO(viewPointGroupStatDTO, viewPointStats));
        }
        return viewPointGroupStats;
    }

    /**
     * 计算统计结果，转换为ViewpointStatDTO
     *
     * @param viewPointId      观点id
     * @param viewPointName    观点名称
     * @param recordCount      原话数量
     * @param viewPointStatMap 观点统计结果
     * @return ViewpointStatDTO 观点统计DTO
     */
    private ViewpointStatDTO getViewpointStatDTO(String viewPointId, String viewPointName, Integer recordCount, Map<String, Map<String, Integer>> viewPointStatMap) {
        ViewpointStatDTO viewpointStatDTO = new ViewpointStatDTO();
        viewpointStatDTO.setId(viewPointId);
        viewpointStatDTO.setName(viewPointName);
        if (recordCount > 0 && viewPointStatMap.containsKey(viewPointId)) {
            Map<String, Integer> viewPointStatInfo = viewPointStatMap.get(viewPointId);
            Integer negNum = viewPointStatInfo.get(RECORD_SENTI_NEGATIVE_KEY);
            Integer neuNum = viewPointStatInfo.get(RECORD_SENTI_NEUTRAL_KEY);
            Integer posNum = viewPointStatInfo.get(RECORD_SENTI_POSITIVE_KEY);
            Integer mixNum = viewPointStatInfo.get(RECORD_SENTI_MIX_KEY);
            Integer totalNum = viewPointStatInfo.get(RECORD_SENTI_TOTAL_NUM_KEY);
            viewpointStatDTO.setNegNum(negNum);
            viewpointStatDTO.setNegRatio(ChartUtil.calPercentage(negNum, recordCount));
            viewpointStatDTO.setNeuNum(neuNum);
            viewpointStatDTO.setNeuRatio(ChartUtil.calPercentage(neuNum, recordCount));
            viewpointStatDTO.setPosNum(posNum);
            viewpointStatDTO.setPosRatio(ChartUtil.calPercentage(posNum, recordCount));
            viewpointStatDTO.setMixNum(mixNum);
            viewpointStatDTO.setMixRatio(ChartUtil.calPercentage(mixNum, recordCount));
            viewpointStatDTO.setNum(totalNum);
            viewpointStatDTO.setRatio(ChartUtil.calPercentage(totalNum, recordCount));
        }
        return viewpointStatDTO;
    }

    /**
     * 统计观点。
     *
     * @param projectId         项目id
     * @param viewPointRelation 观点的上下级关系
     * @param recordIds         原话记录所有id
     * @return Map<String, Map < String, Integer>> 观点id对应的观点统计结果
     * @author jy.zhao
     * @date 2021/5/13 13:53
     **/
    private Map<String, Map<String, Integer>> statViewpoint(String projectId, Map<String, Set<String>> viewPointRelation, List<String> recordIds) {
        Map<String, Map<String, Integer>> viewStatMap = new HashMap<>();
        // 需要统计的观点
        List<JoinInfoViewpointDO> viewPointList = joinInfoDao.listJoinInfos(projectId, recordIds);
        // 子观点情感统计
        for (JoinInfoViewpointDO viewPoint : viewPointList) {
            // 根据观点id获取对应的统计结果，如果没有，则创建默认的统计结果，
            Map<String, Integer> viewStat = getOrCreate(viewStatMap, viewPoint.getViewId());
            // 累加viewPoint中观点id对应的总数量
            viewStat.replace(RECORD_SENTI_TOTAL_NUM_KEY, viewStat.get(RECORD_SENTI_TOTAL_NUM_KEY) + 1);
            // 累加viewPoint中观点id对应的情感的数量
            String sentiKey = String.valueOf(viewPoint.getViewSenti());
            viewStat.replace(sentiKey, viewStat.get(sentiKey) + 1);
        }
        Map<String, Set<JoinInfoViewpointDO>> JoinInfoMap = viewPointList.stream().collect(Collectors.groupingBy(JoinInfoViewpointDO::getRecordId, Collectors.mapping(item -> item, Collectors.toSet())));
        // 观点组情感统计
        for (Map.Entry<String, Set<String>> entry : viewPointRelation.entrySet()) {
            // 观点组情感标记 0代表负向， 1代表中性 2代表正向
            for (Map.Entry<String, Set<JoinInfoViewpointDO>> joinInfo : JoinInfoMap.entrySet()) {
                int[] sentiMark = new int[3];
                // entry.getValue()为观点组id对应的子观点id
                for (JoinInfoViewpointDO joinInfoViewpointDO : joinInfo.getValue()) {
                    if (entry.getValue().contains(joinInfoViewpointDO.getViewId())) {
                        // 此处使用数组下标对应情感来进行数量统计。由于观点情感值【-1， 2】，所以加1.
                        // 使用数组下标对应的数字来判断原话中的观点情感是否出现。 0代表没有出现，1代表出现
                        sentiMark[joinInfoViewpointDO.getViewSenti() + 1] = 1;
                    }
                }
                Map<String, Integer> viewGroupStat = getOrCreate(viewStatMap, entry.getKey());
                int totalMark = sentiMark[0] + sentiMark[1] + sentiMark[2];
                if (sentiMark[0] == 0 && sentiMark[1] == 0 && sentiMark[2] > 0) {
                    // 出现正向情感
                    viewGroupStat.replace(RECORD_SENTI_POSITIVE_KEY, viewGroupStat.get(RECORD_SENTI_POSITIVE_KEY) + 1);
                } else if (sentiMark[0] == 0 && sentiMark[1] > 0 && sentiMark[2] == 0) {
                    // 中性情感
                    viewGroupStat.replace(RECORD_SENTI_NEUTRAL_KEY, viewGroupStat.get(RECORD_SENTI_NEUTRAL_KEY) + 1);
                } else if (sentiMark[0] > 0 && sentiMark[1] == 0 && sentiMark[2] == 0) {
                    // 负向情感
                    viewGroupStat.replace(RECORD_SENTI_NEGATIVE_KEY, viewGroupStat.get(RECORD_SENTI_NEGATIVE_KEY) + 1);
                } else if (totalMark > 0) {
                    // 混合情感
                    viewGroupStat.replace(RECORD_SENTI_MIX_KEY, viewGroupStat.get(RECORD_SENTI_MIX_KEY) + 1);
                }
                if (totalMark > 0) {
                    viewGroupStat.replace(RECORD_SENTI_TOTAL_NUM_KEY, viewGroupStat.get(RECORD_SENTI_TOTAL_NUM_KEY) + 1);
                }
            }

        }
        return viewStatMap;
    }

    /**
     * 根据观点id获取对应的统计结果，如果没有，则创建默认的统计结果，
     *
     * @param viewStatMap 观点id对应的统计结果
     * @param viewId      观点id
     * @return Map<String, Integer> 返回观点id对应的统计结果
     * @author jy.zhao
     * @date 2021/5/13 13:53
     **/
    public Map<String, Integer> getOrCreate(Map<String, Map<String, Integer>> viewStatMap, String viewId) {
        Map<String, Integer> viewStat = viewStatMap.get(viewId);
        if (Validator.isNull(viewStat)) {
            viewStat = new HashMap<>(DEFAULT_MAP_SIZE);
            viewStat.put(RECORD_SENTI_POSITIVE_KEY, 0);
            viewStat.put(RECORD_SENTI_MIX_KEY, 0);
            viewStat.put(RECORD_SENTI_NEUTRAL_KEY, 0);
            viewStat.put(RECORD_SENTI_NEGATIVE_KEY, 0);
            viewStat.put(RECORD_SENTI_TOTAL_NUM_KEY, 0);
            viewStatMap.put(viewId, viewStat);
        }
        return viewStat;
    }

}
